﻿using System.Runtime.CompilerServices;
using System.Security.Cryptography;

namespace HelenServer.Core;

public static partial class Helen
{
    private const int PbKDF2IterCount = 1000;
    private const int PbKDF2SubkeyLength = 256 / 8;
    private const int SaltSize = 128 / 8;

    public static string ToMD5(string value)
    {
        using var md5 = MD5.Create();

        var buffer = md5.ComputeHash(Encoding.UTF8.GetBytes(value));

        var builder = new StringBuilder();

        foreach (var b in buffer)
        {
            builder.Append(b.ToString("x2"));
        }

        return builder.ToString();
    }

    public static string ToMD5(Stream stream)
    {
        using var md5 = MD5.Create();

        var buffer = md5.ComputeHash(stream);

        var builder = new StringBuilder();

        foreach (var b in buffer)
        {
            builder.Append(b.ToString("x2"));
        }

        return builder.ToString();
    }

    public static string Encrypt(string password)
    {
        if (password == null)
        {
            throw new ArgumentNullException(nameof(password));
        }

        byte[] salt;
        byte[] subkey;
        using (var deriveBytes = new Rfc2898DeriveBytes(password, SaltSize, PbKDF2IterCount))
        {
            salt = deriveBytes.Salt;
            subkey = deriveBytes.GetBytes(PbKDF2SubkeyLength);
        }

        var outputBytes = new byte[1 + SaltSize + PbKDF2SubkeyLength];
        Buffer.BlockCopy(salt, 0, outputBytes, 1, SaltSize);
        Buffer.BlockCopy(subkey, 0, outputBytes, 1 + SaltSize, PbKDF2SubkeyLength);
        return Convert.ToBase64String(outputBytes);
    }

    public static bool Validate(string hashed, string origin)
    {
        if (hashed == null)
        {
            return false;
        }

        if (origin == null)
        {
            throw new ArgumentNullException(nameof(origin));
        }

        var hashedPasswordBytes = Convert.FromBase64String(hashed);

        if (hashedPasswordBytes.Length != (1 + SaltSize + PbKDF2SubkeyLength) || hashedPasswordBytes[0] != 0x00)
        {
            return false;
        }

        var salt = new byte[SaltSize];
        Buffer.BlockCopy(hashedPasswordBytes, 1, salt, 0, SaltSize);
        var storedSubkey = new byte[PbKDF2SubkeyLength];
        Buffer.BlockCopy(hashedPasswordBytes, 1 + SaltSize, storedSubkey, 0, PbKDF2SubkeyLength);

        byte[] generatedSubkey;
        using (var deriveBytes = new Rfc2898DeriveBytes(origin, salt, PbKDF2IterCount))
        {
            generatedSubkey = deriveBytes.GetBytes(PbKDF2SubkeyLength);
        }

        return ByteArraysEqual(storedSubkey, generatedSubkey);
    }

    [MethodImpl(MethodImplOptions.NoOptimization)]
    private static bool ByteArraysEqual(byte[] a, byte[] b)
    {
        if (ReferenceEquals(a, b))
        {
            return true;
        }

        if (a == null || b == null || a.Length != b.Length)
        {
            return false;
        }

        var areSame = true;
        for (var i = 0; i < a.Length; i++)
        {
            areSame &= a[i] == b[i];
        }

        return areSame;
    }
}